﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using CookComputing.XmlRpc;
using EPiAbstractions.Common;
using EPiAbstractions.Common.Tags;
using EPiAbstractions.Community;
using EPiAbstractions.Community.Blog;
using EPiAbstractions.Community.Blog.Security;
using EPiAbstractions.Community.ImageGallery;
using EPiServer.Common;
using EPiServer.Common.Categories;
using EPiServer.Common.Publishing;
using EPiServer.Common.Security;
using EPiServer.Common.Sorting;
using EPiServer.Common.Tags;
using EPiServer.Community.Blog;
using EPiServer.Community.Blog.Security;
using EPiServer.Community.ImageGallery;

namespace JoelAbrahamsson.MetaWeblog
{
    public class MetaWeblogHandler : XmlRpcService
    {
        public MetaWeblogHandler(
            IBlogHandlerFacade blogHandlerFacade,
            IDefaultSecurityHandlerFacade securityHandler, 
            ISecurityHandlerFacade securityHandlerFacade,
            IAuthorHandlerFacade authorHandlerFacade,
            IImageGalleryHandlerFacade imageGalleryHandlerFacade, 
            IBlogCategoryProvider blogCategoryProvider,
            ITagHandlerFacade tagHandlerFacade,
            IBlogTagProvider blogTagProvider,
            IBlogEntryExcerptProvider blogEntryExcerptProvider,
            IBlogEntrySlugProvider blogEntrySlugProvider,
            IBlogProvider blogProvider)
        {
            BlogHandlerFacade = blogHandlerFacade;
            SecurityHandler = securityHandler;
            SecurityHandlerFacade = securityHandlerFacade;
            AuthorHandlerFacade = authorHandlerFacade;
            ImageGalleryHandlerFacade = imageGalleryHandlerFacade;
            BlogCategoryProvider = blogCategoryProvider;
            TagHandlerFacade = tagHandlerFacade;
            BlogTagProvider = blogTagProvider;
            BlogEntryExcerptProvider = blogEntryExcerptProvider;
            BlogEntrySlugProvider = blogEntrySlugProvider;
            BlogProvider = blogProvider;
        }

        public IBlogHandlerFacade BlogHandlerFacade { get; private set; }

        public IDefaultSecurityHandlerFacade SecurityHandler { get; private set; }

        public ISecurityHandlerFacade SecurityHandlerFacade { get; private set; }

        public IAuthorHandlerFacade AuthorHandlerFacade { get; private set; }

        public IImageGalleryHandlerFacade ImageGalleryHandlerFacade { get; private set; }

        public IBlogCategoryProvider BlogCategoryProvider { get; private set; }

        public ITagHandlerFacade TagHandlerFacade { get; private set; }

        public IBlogTagProvider BlogTagProvider { get; private set; }

        public IBlogEntryExcerptProvider BlogEntryExcerptProvider { get; private set; }

        public IBlogEntrySlugProvider BlogEntrySlugProvider { get; private set; }

        public IBlogProvider BlogProvider { get; private set; }

        [XmlRpcMethod("metaWeblog.newPost")]
        public string AddPost(string blogid, string username, string password,
            Post post, bool publish)
        {
            Blog blog = GetBlog(blogid);
            
            IUser user = GetValidatedUser(blog, username, password);

            UserAuthor author = GetOrCreateUserAuthor(user);
            Entry entry = new Entry(blog, author, post.title, post.description);
            if (post.dateCreated <= default(DateTime))
                post.dateCreated = DateTime.Now;
            SetEntryProperties(entry, post, author);
            
            entry = BlogHandlerFacade.AddEntry(entry);

            return entry.ID.ToString();
        }

        public virtual void SetEntryProperties(Entry entry, Post post, IAuthor author)
        {
            entry.Title = post.title;
            entry.Content = post.description;
            entry.PublicationStart = post.dateCreated;
            BlogEntryExcerptProvider.SetExcerpt(entry, post.mt_excerpt);
            BlogEntrySlugProvider.SetSlug(entry, post.wp_slug);
            UpdateEntryCategories(entry, post.categories);
            UpdateEntryTags(entry, post.mt_keywords, author);
        }

        public virtual void UpdateEntryCategories(Entry entry, string[] categories)
        {
            entry.Categories.Clear();
            
            if(categories == null)
                return;
            
            AddCategories(entry, categories);
        }

        private void AddCategories(Entry entry, string[] categories)
        {
            foreach (string categoryTitle in categories)
            {
                ICategory category = BlogCategoryProvider.ParseCategory(categoryTitle);
                
                if(category == null)
                    continue;

                entry.Categories.Add(category);
            }
        }

        public virtual void UpdateEntryTags(Entry entry, string tagsString, IAuthor tagger)
        {
            if (tagsString == null)
                tagsString = string.Empty;

            string[] tags = tagsString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < tags.Length; i++)
            {
                tags[i] = tags[i].Trim();
            }

            AddTagsNotAlreadyAdded(tags, entry, tagger);

            RemoveTagsNotIncludedInList(tags, entry);
        }

        private void AddTagsNotAlreadyAdded(string[] tags, Entry entry, IAuthor tagger)
        {
            List<string> tagsToAddNames = new List<string>();
            foreach (string tagName in tags)
            {
                if(!entry.EntityTags.ContainsTag(tagName))
                    tagsToAddNames.Add(tagName);
            }
            
            AddTags(entry, tagsToAddNames, tagger);
        }

        private void AddTags(Entry entry, List<string> tagsToAddNames, IAuthor tagger)
        {
            foreach (string tagName in tagsToAddNames)
            {
                ITag tag = TagHandlerFacade.GetTag(tagName);
                
                if(tag == null)
                {
                    tag = TagHandlerFacade.AddTag(new Tag(tagName));
                }
                
                EntityTag entityTag = new EntityTag(tag, tagger);
                entry.EntityTags.Add(entityTag);
            }
        }

        private void RemoveTagsNotIncludedInList(string[] tags, Entry entry)
        {
            List<IEntityTag> tagsToRemove = new List<IEntityTag>();
            foreach (IEntityTag entityTag in entry.EntityTags)
            {
                if (tags.Count(tagName => tagName.Equals(entityTag.Tag.Name, StringComparison.InvariantCultureIgnoreCase)) == 0)
                    tagsToRemove.Add(entityTag);
            }

            foreach (IEntityTag tag in tagsToRemove)
            {
                entry.EntityTags.Remove(tag);
            }
        }

        public virtual Blog GetBlog(string blogid)
        {
            int parsedBlogID = int.Parse(blogid);
            return BlogHandlerFacade.GetBlog(parsedBlogID);
        }

        public virtual UserAuthor GetOrCreateUserAuthor(IUser user)
        {
            UserAuthor userAuthor = AuthorHandlerFacade.GetUserAuthor(user) as UserAuthor;
            if ((IAuthor)userAuthor != default(IAuthor))
                return userAuthor;

            userAuthor = new UserAuthor(user);
            return (UserAuthor)AuthorHandlerFacade.AddAuthor(userAuthor);
        }

        public virtual IUser GetValidatedUser(Blog blog, string username, string password)
        {
            IUser user;
            if (!SecurityHandler.AuthenticateUser(username, password, out user))
                    throw new XmlRpcFaultException(0, "User is not valid!");

            if(blog.Author != null && blog.Author is UserAuthor && ((UserAuthor)blog.Author).User.ID == user.ID)
                return user;

            BlogAccessRights accessRights = SecurityHandlerFacade.GetTotalBlogAccessRights(blog, user);

            if (!accessRights.CreateEntry)
                throw new XmlRpcFaultException(0, "User does not have sufficient access rights!");

            if (!accessRights.UpdateEntry)
                throw new XmlRpcFaultException(0, "User does not have sufficient access rights!");

            return user;
        }

        [XmlRpcMethod("metaWeblog.editPost")]
        public bool UpdatePost(string postid, string username, string password,
            Post post, bool publish)
        {
            Entry entry = GetEntry(postid);
            if(entry == null)
                return false;

            Blog blog = entry.Blog;
            if (blog == null)
                return false;

            IUser user = GetValidatedUser(blog, username, password);

            if (post.dateCreated <= default(DateTime))
                post.dateCreated = entry.PublicationStart;
            
            UpdateEntry(entry, post, user);

            return true;
        }

        private void UpdateEntry(Entry entry, Post post, IUser user)
        {
            entry = (Entry)entry.Clone();
            SetEntryProperties(entry, post, GetOrCreateUserAuthor(user));
            BlogHandlerFacade.UpdateEntry(entry);
        }

        public virtual Entry GetEntry(string postid)
        {
            int entryID;
            if (int.TryParse(postid, out entryID))
                return BlogHandlerFacade.GetEntry(entryID);

            return null;
        }

        [XmlRpcMethod("metaWeblog.getPost")]
        public Post GetPost(string postid, string username, string password)
        {
            Entry entry = GetEntry(postid);
            if (entry == null)
                throw new XmlRpcFaultException(0, "No post exists with the specified ID");

            Blog blog = entry.Blog;
            if (blog == null)
                throw new XmlRpcFaultException(0, "The post with the specified ID does not have a blog associated to it");

            GetValidatedUser(blog, username, password);

            return CreatePost(entry);
        }

        public virtual Post CreatePost(Entry entry)
        {
            string[] categories = GetEntryCategories(entry);
            string tags = GetEntryTags(entry);

            return new Post
                       {
                           postid = entry.ID, 
                           title = entry.Title, 
                           description = entry.Content, 
                           categories = categories,
                           mt_keywords = tags,
                           mt_excerpt = BlogEntryExcerptProvider.GetExcerpt(entry),
                           wp_slug = BlogEntrySlugProvider.GetSlug(entry),
                           dateCreated = entry.PublicationStart
                       };
        }

        public virtual string[] GetEntryCategories(Entry entry)
        {
            List<string> categories = new List<string>();
            
            foreach (ICategory category in entry.Categories)
            {
                categories.Add(BlogCategoryProvider.CreateCategoryInfo(category).title);
            }

            return categories.ToArray();
        }

        public virtual string GetEntryTags(Entry entry)
        {
            StringBuilder tags = new StringBuilder(); 
            foreach(ITag tag in entry.Tags)
            {
                tags.Append(tag.Name);
                tags.Append(", ");
            }
            if (tags.Length > 0)
                tags.Remove(tags.Length - 2, 2);
            
            return tags.ToString();
        }

        [XmlRpcMethod("metaWeblog.getCategories")]
        public CategoryInfo[] GetCategories(string blogid, string username, string password)
        {
            Blog blog = GetBlog(blogid);

            GetValidatedUser(blog, username, password);
            
            return BlogCategoryProvider.GetBlogCategories(blog);
        }

        [XmlRpcMethod("wp.getTags")]
        public TagInfo[] GetTags(string blogid, string username, string password)
        {
            Blog blog = GetBlog(blogid);
            
            GetValidatedUser(blog, username, password);

            List<TagInfo> tags = new List<TagInfo>();
            foreach (Tag tag in BlogTagProvider.GetTags(blog))
            {
                tags.Add(new TagInfo { name = tag.Name });
            }
            
            return tags.ToArray();
        }

        [XmlRpcMethod("metaWeblog.getRecentPosts")]
        public Post[] GetRecentPosts(string blogid, string username, string password,
            int numberOfPosts)
        {
            Blog blog = GetBlog(blogid);

            GetValidatedUser(blog, username, password);

            List<Post> posts = new List<Post>();
            EntryCollection recentEntries = 
                BlogHandlerFacade.GetEntries(blog, 1, numberOfPosts, new [] { new EntrySortOrder(EntrySortField.LastUpdate, SortingDirection.Descending) });
            
            if(recentEntries == null)
                return new Post[0];
            
            foreach (Entry entry in recentEntries)
            {
                posts.Add(CreatePost(entry));
            }
            
            return posts.ToArray();
        }

        [XmlRpcMethod("metaWeblog.newMediaObject")]
        public MediaObjectInfo NewMediaObject(string blogid, string username, string password,
            MediaObject mediaObject)
        {
            Blog blog = GetBlog(blogid);
            
            IUser user = GetValidatedUser(blog, username, password);
            
            Image image = AddImage(blog, mediaObject, user);

            Thumbnail thumbnail = image.GetThumbnail(image.Width, image.Height, ThumbnailFormat.ReduceAndCrop);
            
            MediaObjectInfo objectInfo = new MediaObjectInfo();
            objectInfo.url = thumbnail.Url;

            return objectInfo;
        }

        private Image AddImage(Blog blog, MediaObject mediaObject, IUser user)
        {
            Image image = null;
            using (MemoryStream stream = new MemoryStream(mediaObject.bits))
            {
                image = new Image(mediaObject.name, mediaObject.name, stream, blog.ImageGallery, PublishState.Published, user);
                image = ImageGalleryHandlerFacade.AddImage(image);
            }
            return image;
        }

        [XmlRpcMethod("blogger.deletePost")]
        public bool DeletePost(string key, string postid, string username, string password, bool publish)
        {
            Entry entry = GetEntry(postid);
            if(entry == null)
                return false;

            GetValidatedUser(entry.Blog, username, password);

            BlogHandlerFacade.RemoveEntry(entry);
            
            return true;

        }

        [XmlRpcMethod("blogger.getUsersBlogs")]
        public BlogInfo[] GetUsersBlogs(string key, string username, string password)
        {
            IUser user;
            if (!SecurityHandler.AuthenticateUser(username, password, out user))
                throw new XmlRpcFaultException(0, "User is not valid!");

            return BlogProvider.GetUsersBlogs(user);
        }
    }
}
